/*
 * Flashlib - QB9 common Flash library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

 
package com.qb9.flashlib.motion
{
	import com.qb9.flashlib.tasks.*;
	import com.qb9.flashlib.math.QMath;
	import com.qb9.flashlib.utils.ObjectUtil;
	
	public class MoveTo extends Task
	{
		protected static const INFINITY:Number = Number.MAX_VALUE;
		
		/** Velocity (units / second) */
		public var velocity:Number = 0;
		/** Maximum velocity (units / second) */
		public var maxVelocity:Number = INFINITY;
		
		/** Acceleration (units / second²) */
		public var acceleration:Number = INFINITY;

		/** Angular velocity (units / second). */
		public var angularVelocity:Number = 0;
		/** Maximum angular Velocity (units / second) */
		public var maxAngularVelocity:Number = INFINITY;
		
		public var angularAcceleration:Number = INFINITY;
		
		protected var obj:Object;
		public var target:Object;
		
		public var updateRotation:Boolean = true;		
		protected var _rotation:Number = 0;
		
		/** If this distance is reached, the object will jump to its destination */
		public var distanceThreshold:Number = 2;
		/** Whether the task should end if the target is reached */
		public var endWhenTargetReached:Boolean = true;

		public function MoveTo(obj:Object, target:Object, options:Object = null)
		{
			this.obj = obj;
			this.target = target;
			
			if (options)
				ObjectUtil.copy(options, this);
				
			updateRotation = updateRotation && 'rotation' in obj;
			
			if (updateRotation)
				_rotation = fromASRot(obj.rotation);
		}
		
		public function get x():Number
		{ 
			return obj.x; 
		}
		public function set x(v:Number):void
		{ 
			obj.x = v; 
		}
		
		public function get y():Number
		{ 
			return obj.y;
		}		
		public function set y(v:Number):void
		{ 
			obj.y = v; 
		}
		
		/** Rotation [0,360] */
		public function get rotation():Number
		{
			return _rotation;
		}
		public function set rotation(v:Number):void 
		{
			_rotation = v;
			if (updateRotation)
				obj.rotation = toASRot(rotation);
		}
		
		// FIXME: Will do without vectors, then it can be cleaned
		public override function update(milliseconds:uint):void
		{
			var secs:Number = milliseconds / 1000.0;
			
			var dx:Number = target.x - x;
			var dy:Number = target.y - y;
			var angleRad:Number = Math.atan2(-dy, dx);
			var angleDeg:Number = QMath.rad2deg(angleRad);
			
			var diff:Number = angleDifference(rotation, angleDeg);
			var s:int = QMath.sign(diff);
			
			angularVelocity += s * angularAcceleration;
			angularVelocity = QMath.clamp(angularVelocity, -maxAngularVelocity, maxAngularVelocity);
			
			rotation += minMod(diff, angularVelocity * secs);
			
			velocity = Math.min(maxVelocity, velocity + acceleration * secs);
			
			var radRotation:Number = QMath.deg2rad(rotation);
			this.x += velocity * Math.cos(radRotation);
			this.y += velocity * -Math.sin(radRotation);
			
			var distance:Number = Math.sqrt(dx*dx + dy*dy);
			if (distance <= distanceThreshold)
			{
				distance = 0;
				this.x = target.x;
				this.y = target.y;
			}
			
			if (distance == 0 && endWhenTargetReached)
				taskComplete();
		}
		
		protected static function fromASRot(deg:Number):Number
		{
			return Math.abs(deg - 270);
		}
		
		protected static function toASRot(deg:Number):Number
		{
			return -deg + 270;
		}
		
		protected static function angleDifference(start:Number, end:Number):Number
		{
			start %= 360;
			end %= 360;
			
			var diff1:Number = end - start;
			var diff2:Number = diff1 - 360;
			
			return minMod(diff1, diff2);
		}
		
		protected static function minMod(n:Number, m:Number):Number
		{
			return Math.abs(n) < Math.abs(m) ? n : m;
		}
	}
}
